﻿using LightCAD.MathLib;
using System;

namespace QdArch
{
    public class QdWindowAttribute : CptAttribute
    {
        public QdWindowAttribute(string category, string subCategorys) : base(category, subCategorys)
        {

        }

        public const string BuiltinUuid = "{4D611A24-3166-4EB9-919C-DBB3048ED043}";
        public override LcComponentDefinition GetBuiltinCpt(string subCategory)
        {
            return new QdWindowDef(BuiltinUuid, "内置", "普通窗");
        }
    }

    [QdWindowAttribute("窗", "普通窗")]
    public class QdWindowDef : LcComponentDefinition
    {
        static QdWindowDef()
        {
            CptTypeParamsDef<QdWindowDef>.ParamsDef = new ParameterSetDefinition {
                new ParameterDefinition { DataType = LcDataType.String, Name = "MaterialId", DisplayName = "材质", },
            };
        }
        public QdWindowDef()
        {
            this.TypeParameters = new LcParameterSet(CptTypeParamsDef<QdWindowDef>.ParamsDef);
        }
        public QdWindowDef(string uuid, string name, string subCategory)
            : base(uuid, name, "窗", subCategory, new LcParameterSet(CptTypeParamsDef<QdWindowDef>.ParamsDef))
        {
            this.Parameters = new ParameterSetDefinition()
            {
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Thickness", DisplayName = "厚度", },
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Width", DisplayName = "宽度", },
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Height", DisplayName = "高度", },
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Bottom", DisplayName= "底高度" } ,
                new ParameterDefinition { DataType = LcDataType.Bool, Name = "IsLeft", DisplayName = "是否左开", },
                new ParameterDefinition { DataType = LcDataType.Bool, Name = "IsNear", DisplayName = "是否在墙体左侧", },
                new ParameterDefinition { DataType = LcDataType.Double, Name = "Angle", DisplayName = "角度(0-90)", },
                new ParameterDefinition { DataType = LcDataType.Bool, Name = "IsSingle", DisplayName = "单扇/双扇", },
            };

            this.Curve2dProvider = new Curve2dProvider("普通窗")
            {
                GetCurve2dsFunc = (p) =>
                {
                    ListEx<Curve2d> curves = null;

                    //var isSingle = p.GetValue<bool>("Size");
                    if (false)
                    {
                        curves = GetShape_单线窗(p);
                    }
                    else
                    {
                        curves = GetShape_三线窗(p);
                    }
                    return new Curve2dGroupCollection() { new Curve2dGroup()
                        {
                            Curve2ds=curves
                        }};
                }
            };
            var mat = MaterialManager.DefaultMat;
            if (subCategory == "普通窗")
                mat = MaterialManager.GetMaterial(MaterialManager.GlassUuid);
            var frameMat = MaterialManager.GetMaterial(MaterialManager.Metal2Uuid); 

            this.Solid3dProvider = new Solid3dProvider("普通窗")
            {
                AssignMaterialsFunc = (c, s) => {
                    if (s.Name=="Glass")
                    {
                        return new LcMaterial[] { mat };
                    }
                    return new LcMaterial[] { frameMat };
                },
                GetSolid3dsFunc = (p) =>
                {
                    return GetShape3D(p);
                }
            };
        }



        public ListEx<Curve2d> GetShape_单线窗(LcParameterSet pset)
        {

            var sc = new Shape2dCreator();

            var w = (double)pset["Width"];
            //var th = (double)pset["Thickness"];
            var isLeft = (bool)pset["IsLeft"];
            var isNear = (bool)pset["IsNear"];

            var pL = sc.Point(-w / 2, 0);
            var pR = sc.Point(w / 2, 0);
            sc.DrawLine(pL.Clone(), pR.Clone());
            //var pLNear = sc.RotatePoint(pL, pR, 45);
            //var pLFar = sc.RotatePoint(pL, pR, -45);
            //var pRNear = sc.RotatePoint(pR, pL, -45);
            //var pRFar = sc.RotatePoint(pR, pL, 45);
            //if (isLeft && isNear)
            //{
            //    sc.DrawArc(pL, w, -45, 0);
            //    sc.DrawLine(pL, pRNear);
            //}
            //else if (isLeft && !isNear)
            //{
            //    sc.DrawArc(pL, w, 0, +45);
            //    sc.DrawLine(pL, pRFar);
            //}
            //else if (!isLeft && isNear)
            //{
            //    sc.DrawArc(pR, w, 180, 180 + 45);
            //    sc.DrawLine(pR, pLNear);
            //}
            //else if (!isLeft && !isNear)
            //{
            //    sc.DrawArc(pR, w, 180 - 45, 180);
            //    sc.DrawLine(pR, pLFar);
            //}

            return sc.Curves;
        }

        public ListEx<Curve2d> GetShape_三线窗(LcParameterSet pset)
        {

            var sc = new Shape2dCreator();

            var w = (double)pset["Width"];
            var t = (double)pset["Thickness"];
            //var th = (double)pset["Thickness"];
            var isLeft = (bool)pset["IsLeft"];
            var isNear = (bool)pset["IsNear"];

            // pTL_______pTR
            //|             |
            // pCL ———pcR
            //|             |
            // pBL________pBR

            var pTL = sc.Point(-w / 2, t / 2);
            var pTR = sc.Point(w / 2, t / 2);
            var pCL = sc.Point(-w / 2, 0);
            var pCR = sc.Point(w / 2, 0);
            var pBL = sc.Point(-w / 2, -t / 2);
            var pBR = sc.Point(w / 2, -t / 2);

            sc.DrawLine(pTL.Clone(), pTR.Clone());
            sc.DrawLine(pCL.Clone(), pCR.Clone());
            sc.DrawLine(pBL.Clone(), pBR.Clone());
            sc.DrawLine(pTL.Clone(), pBL.Clone());
            sc.DrawLine(pTR.Clone(), pBR.Clone());
            //var pLNear = sc.RotatePoint(pL, pR, 45);
            //var pLFar = sc.RotatePoint(pL, pR, -45);
            //var pRNear = sc.RotatePoint(pR, pL, -45);
            //var pRFar = sc.RotatePoint(pR, pL, 45);
            //if (isLeft && isNear)
            //{
            //    sc.DrawArc(pL, w, -45, 0);
            //    sc.DrawLine(pL, pRNear);
            //}
            //else if (isLeft && !isNear)
            //{
            //    sc.DrawArc(pL, w, 0, +45);
            //    sc.DrawLine(pL, pRFar);
            //}
            //else if (!isLeft && isNear)
            //{
            //    sc.DrawArc(pR, w, 180, 180 + 45);
            //    sc.DrawLine(pR, pLNear);
            //}
            //else if (!isLeft && !isNear)
            //{
            //    sc.DrawArc(pR, w, 180 - 45, 180);
            //    sc.DrawLine(pR, pLFar);
            //}

            return sc.Curves;
        }


        private Solid3dCollection GetShape3D(LcParameterSet pset)
        {
            var thickness = (double)pset["Thickness"];
            var glassThickness = 10;
            var frameWidth = 50;
            var bottom = (double)pset["Bottom"];
            var height = (double)pset["Height"];
            var width = (double)pset["Width"];
            var start = new Vector3(-width / 2, 0, bottom);
            var end = new Vector3(width / 2, 0, bottom);
            var lineVec = new Vector3().SubVectors(end, start);
            var len = lineVec.Length();
            var xAxis = lineVec.Clone().Normalize();
            var yAxis = VectorUtil.ZAxis.Clone();
            var zAxis = new Vector3().CrossVectors(xAxis, yAxis).Normalize();
            var coodMat = new Matrix4();
            coodMat.MakeBasis(xAxis, yAxis, zAxis);
            coodMat.SetPosition(start);
            var glassShape = ShapeUtil.Rect(frameWidth, frameWidth, len - frameWidth, height - frameWidth);
            var frameShape = ShapeUtil.Rect(0, 0, len, height);
            frameShape.holes.Add(glassShape);
            var glassGeoData = GeoUtil.GetStretchGeometryData(glassShape, coodMat, glassThickness / 2, -glassThickness / 2);
            var frameGeoData = GeoUtil.GetStretchGeometryData(frameShape, coodMat, thickness / 2, -thickness / 2);
            var solids = new Solid3dCollection()
            {
                new Solid3d()
                {
                    Name="Glass",
                    Geometry = new LightCAD.MathLib.GeometryData()
                    {
                        Verteics = glassGeoData.Position.ToArray(),
                        Indics = glassGeoData.Index.ToArray(),
                        Groups = new GeometryGroup[1]
                        {
                            new GeometryGroup{ Name = "Glass", Start = 0, Count = glassGeoData.Index.Length, MaterialIndex = 0 },
                        }
                    }
                },
                new Solid3d()
                {        
                    Name="Frame",
                    Geometry = new LightCAD.MathLib.GeometryData()
                    {
                        Verteics = frameGeoData.Position.ToArray(),
                        Indics = frameGeoData.Index.ToArray(),
                        Groups = new GeometryGroup[1]
                        {
                            new GeometryGroup{ Name = "Frame", Start = 0, Count = frameGeoData.Index.Length, MaterialIndex = 0 },
                        }
                    }
                }
            };
            return solids;
        }
    }

    public class QdWindowInstance : LcComponentInstance
    {
        public QdWindowDef WindowDef { get => this.Definition as QdWindowDef; set => this.Definition = value; }
        /// <summary>
        /// 在墙上用中心插入
        /// </summary>
        public QdWall Wall { get; set; }

        private Vector2 startPoint = null;
        public Vector2 StartPoint
        {
            get
            {
                if (startPoint == null)
                {
                    var wallLineDir = this.Wall.BaseLine.Dir;
                    var offset = wallLineDir * (this.Width / 2);
                    startPoint = this.Position.ToVector2() - offset;
                }
                return startPoint;
            }
        }
        public Vector2 endPoint = null;
        public Vector2 EndPoint
        {
            get
            {
                if (endPoint == null)
                {
                    var wallLineDir = this.Wall.BaseLine.Dir;
                    var offset = wallLineDir * (this.Width / 2);
                    endPoint = this.Position.ToVector2() + offset;
                }
                return endPoint;
            }
        }
        public Vector2 controlPoint = null;
        public Vector2 ControlPoint
        {
            get
            {
                if (controlPoint == null)
                {
                    var isRight = !this.IsLeft; //是否在插入点右侧
                    var isUp = !this.IsNear; //是否在墙线上方，即墙线左侧

                    //第三个点的旋转角度， 经观察发现三个点在半径相同的同一个圆上，圆心为doorRef.InsertPoint
                    double angle = this.Wall.BaseLine.Dir.Angle();
                    double angle45 = Math.PI / 4;
                    if (isRight && isUp)
                    {
                        angle += angle45;
                    }
                    else if (!isRight && isUp)
                    {
                        angle += Math.PI / 2 + angle45;
                    }
                    else if (!isRight && !isUp)
                    {
                        angle += Math.PI + angle45;
                    }
                    else if (isRight && !isUp)
                    {
                        angle += Math.PI * 1.5 + angle45;
                    }

                    var lineDir = Vector2.RotateInRadian(new Vector2(1, 0), angle);
                    var lineOffset = lineDir * (this.Width / 2);

                    controlPoint = this.Position.ToVector2() + lineOffset;
                }
                return controlPoint;

            }
        }

        public double Thickness
        {
            get
            {
                return Convert.ToDouble(this.Parameters["Thickness"]);
            }
            set
            {
                this.Parameters["Thickness"] = value;
            }
        }
        public double Width
        {
            get
            {
                return Convert.ToDouble(this.Parameters["Width"]);
            }
            set
            {
                this.Parameters["Width"] = value;
            }
        }
        public double Height
        {
            get
            {
                return Convert.ToDouble(this.Parameters["Height"]);
            }
            set
            {
                this.Parameters["Height"] = value;
            }
        }
        public double Bottom
        {
            get
            {
                return Convert.ToDouble(this.Parameters["Bottom"]);
            }
            set
            {
                this.Parameters["Bottom"] = value;
            }
        }
        public bool IsLeft
        {
            get
            {
                return Convert.ToBoolean(this.Parameters["IsLeft"]);
            }
            set
            {
                this.Parameters["IsLeft"] = value;
            }
        }
        public bool IsNear
        {
            get
            {
                return Convert.ToBoolean(this.Parameters["IsNear"]);
            }
            set
            {
                this.Parameters["IsNear"] = value;
            }
        }

        public QdWindowInstance(QdWindowDef windowRef) : base(windowRef)
        {
            this.Type = ArchElementType.Window;
        }
        public QdWindowInstance(double thickness, double width, double height, double bottom, bool isLeft, bool isNear, double angle, bool isSingle, QdWindowDef windowDef) : this(windowDef)
        {
            this.Parameters.SetValue("Thickness", thickness);
            this.Parameters.SetValue("Width", width);
            this.Parameters.SetValue("Height", height);
            this.Parameters.SetValue("Bottom", bottom);
            this.Parameters.SetValue("IsLeft", isLeft);
            this.Parameters.SetValue("IsNear", isNear);
            this.Parameters.SetValue("Angle", angle);
            this.Parameters.SetValue("IsSingle", isSingle);
        }
        public override Box2 GetBoundingBox()
        {
            if (this.Parameters == null || this.Parameters.Values.Length == 0)
            {
                return Box2.Empty;
            }

            return new Box2(this.Position.ToVector2(), Width, Height);
        }

        public override void ResetCache()
        {
            base.ResetCache();
            this.startPoint = null;
            this.endPoint = null;
            this.controlPoint = null;

            this.Wall.UpdateLcComponent(this, nameof(QdWindowInstance), AssociateType.Cross);
        }

        public override bool IntersectWithBox(Polygon2d testPoly, List<RefChildElement> intersectChildren = null)
        {
            Box2 thisBox = this.BoundingBox;
            return thisBox.IntersectsBox(testPoly.BoundingBox);
        }

        public override bool IncludedByBox(Polygon2d testPoly, List<RefChildElement> includedChildren = null)
        {
            Box2 thisBox = this.BoundingBox;
            return thisBox.IntersectsBox(testPoly.BoundingBox);
        }

        public override void OnRemoveAfter()
        {
            this.Wall?.RemoveLcComponent(this, nameof(QdWindowInstance), AssociateType.Cross);
        }

        public override void Mirror(Vector2 axisStart, Vector2 axisEnd)
        {
            return;
        }

        public override LcElement Clone()
        {
            var clone = new QdWindowInstance(this.WindowDef);
            clone.Copy(this);
            clone.Initilize(this.Document);
            return clone;
        }

        public override void Copy(LcElement src)
        {
            base.Copy(src);
            var win = (QdWindowInstance)src;
            this.Wall = win.Wall;

        }

        public override void ReadObjectRefs<T>(ICollection<T> objs)
        {
            if (this.Wall == null)
            {
                var wallId = (long)this.UserData["WallId"];
                this.Wall = objs.FirstOrDefault(o => o.Id == wallId) as QdWall;
                if (this.WindowDef != null)
                {
                    this.Wall.AddLcComponent(this, nameof(QdWindowInstance), AssociateType.Cross);
                }
            }
        }
        public override void WriteProperties(Utf8JsonWriter writer, JsonSerializerOptions soptions)
        {
            base.WriteProperties(writer, soptions);
            writer.WriteNumberProperty("WallId", this.Wall.Id);
            writer.WriteVector2dProperty(nameof(this.StartPoint), this.StartPoint);
            writer.WriteVector2dProperty(nameof(this.EndPoint), this.EndPoint);
            writer.WriteVector2dProperty(nameof(this.ControlPoint), this.ControlPoint);
        }

        public override void ReadProperties(ref JsonElement jele)
        {
            base.ReadProperties(ref jele);
            if (this.UserData == null)
            {
                this.UserData = new Dictionary<string, object>();
            }
            this.UserData["WallId"] = jele.ReadIdProperty("WallId");
        }
    }
}